/**
 * src：创建读取流，可直接src(‘源文件路径’) 来读取文件流
 * dest：创建写入流，可直接dest(‘目标文件路径’) 来将文件流写入目标文件中
 * parallel：创建一个并行的构建任务，可并行执行多个构建任务 parallel(‘任务1’，‘任务2’，‘任务3’，…) 异步执行
 * series：创建一个串行的构建任务，会按照顺序依次执行多个任务 series(‘任务1’，‘任务2’，‘任务3’，…) 同步执行
 * watch：对文件进行监视，当文件发生变化时，可执行相关任务
 */
// 引入 gulp 相关 api
const { src, dest, series, parallel, watch } = require('gulp')
// 导入相关插件库
const del = require('delete') // 导入目录/文件删除库
// const notify = require('gulp-notify') // 导入通知插件
// const rename = require('gulp-rename') // 导入文件重命名插件
// const htmlmin = require('gulp-htmlmin') // 导入 html 压缩插件
const sass = require('gulp-sass')(require('sass')) // 导入 scss 转换 css 插件
// const less = require('gulp-less') // 导入 less 转换 css 插件
// const sourcemaps = require('gulp-sourcemaps') // 导入 sourcemaps 源码映射插件
// const cleanCss = require('gulp-clean-css') // 导入 css 压缩插件（多个单词拼接命名使用小驼峰命名法，以便统一 gulp-load-plugins 插件注册使用）
// const autoprefixer = require('gulp-autoprefixer') // 导入 css 压缩插件
// const babel = require('gulp-babel') // 导入 babel 转换 js 语法为 es5 浏览器能识别的语法
// const typescript = require('gulp-typescript') // 导入 ts 解析插件
// const uglify = require('gulp-uglify') // 导入 js 压缩插件
// // 导入图片/字体压缩插件
// const imagemin = require('gulp-imagemin') // 可同时压缩图片与字体
// const webp = require('gulp-webp') // 把 png 图片转为 webp 格式
// const useref = require('gulp-useref') // 导入解析 HTML 文件中的构建块，以用 useref 替换对未优化脚本或样式表的引用
// const gulpif = require('gulp-if') // 导入判断语句 if
// 导入 gulp 插件自动导入并注册插件
// const gulpLoadPlugins = require('gulp-load-plugins')
// const plugins = gulpLoadPlugins()
// 配置 gulp-load-plugins (默认配置项如下)
/*
gulpLoadPlugins({
  DEBUG: false,   // 设置为 true 时，该插件会将信息记录到控制台。对于错误报告和问题调试很有用
  config: 'package.json', // 定义从哪里搜索插件
  pattern: ['gulp-*', 'gulp.*', '@*!/gulp{-,.}*'], // 将会去搜索的数据
  scope: ['dependencies', 'devDependencies', 'peerDependencies'], //在被搜索的文件中要去查看哪些键值
  replaceString: /^gulp(-|\.)/, // 将模块添加到上下文时要从模块名称中删除的内容，例如gulp-rename在使用是为$.rename()即可，无需前缀
  lazy: true, // 插件是否应按需延迟加载
  overridePattern: true, // 如果为true，则覆盖内置模式。 否则，扩展内置模式匹配器列表。
  camelize: true, //如果为true，则将带 - 字符的插件名称转换为驼峰式
  rename: {}, //重命名插件的映射
  renameFn: function (name) { ... }, //处理插件重命名的功能（默认有效）
  postRequireTransforms: {}, // see documentation below
  maintainScope: true // 切换加载所有npm范围，如非作用域包
)}
*/
const plugins = require('gulp-load-plugins')() // 使用默认配置

/* 自定义路径配置相关 */
const cwd = process.cwd() // 获取项目绝对路径
let config = {
  mode: 'development',
  entry: 'def.config.js',
  output: 'dist',
  static: 'public',
  src: 'src',
  paths: {
    pages: '*.html',
    styles: 'assets/styles/**/*',
    scripts: 'assets/scripts/**/*',
    images: 'assets/images/**/*',
    icons: 'assets/icons/**/*',
    fonts: 'assets/fonts/**/*'
  },
  devServer: {
    notify: false,
    port: 80,
    open: true
  }
}

try {
  const loadConfig = require(`${cwd}/${config.entry}`)
  config = { ...config, ...loadConfig }
} catch (e) {}

const configMode = ['development', 'production']
if (!configMode.includes(config.mode)) {
  throw new Error('配置文件 mode 属性只能取值为 development 或 production ')
}

const { output: buildPath, static: staticPath, src: srcPath, paths, mode, devServer } = config
const { pages: pagesPath, styles: stylesPath, scripts: scriptsPath, images: imagesPath, icons: iconsPath, fonts: fontsPath } = paths
const { notify, port, open } = devServer
const isDev = mode === 'development'

// 导入浏览器同步测试服务
const browserSync = require('browser-sync')
// 创建开发服务器
const bs = browserSync.create()

// 定义打包文件 dist 清除任务 clean
const clean = () => {
  // 打包前删除 dist 目录
  return del([buildPath])
}

// 定义html相关任务 page
const page = () => {
  return src(`${pagesPath}`, { cwd: srcPath })
    /* // 转换流使用 html 压缩插件
    .pipe(plugins.htmlmin({
        collapseWhitespace: true,
        minifyCSS: true,
        minifyJS: true
      })) // 去除折行与压缩CSS与JS */
    .pipe(dest(buildPath))
    .pipe(bs.reload({ stream: true })) // 取代 bs init 下 files 监听目录配置
}
// 定义css相关任务 style
const style = () => {
  return /* 读取流 */ src(stylesPath, { base: srcPath, cwd: srcPath }) // base: srcPath 可以保留打包生成原目录层级
    /* 转换流 */
    // 使用 sourcemaps 初始化映射
    .pipe(plugins.if(isDev, plugins.sourcemaps.init()))
    // 使用 sass 转换插件
    .pipe(plugins.if(/\.scss$/, sass().on('error', sass.logError)))
    // 使用 less 转换插件
    .pipe(plugins.if(/\.less$/, plugins.less()))
    // 使用 css 前缀添加插件
    .pipe(plugins.autoprefixer())
    // 使用 css 压缩插件
    // .pipe(plugins.cleanCss({ compatibility: 'ie8' })) // 兼容 ie8
    // 使用文件重命名插件
    .pipe(plugins.rename({ suffix: '.min' })) // 为css压缩文件添加后缀名 .min
    // 使用 sourcemaps 写入映射
    .pipe(plugins.if(isDev, plugins.sourcemaps.write('.')))
    /* 写入流 */
    .pipe(dest(buildPath))
    .pipe(bs.reload({ stream: true })) // 取代 bs init 下 files 监听目录配置
}
// 定义js相关任务 script
const script = () => {
  // 默认文件生成到dest方法指定的dist目录下，新增与src方法重复的开头路径base: srcPath,就会保留打包生成文件所在的层级格式
  return /* 读取流 */ src(scriptsPath, { base: srcPath, cwd: srcPath })
    /* 转换流 */
    // 使用 sourcemaps 初始化映射
    .pipe(plugins.if(isDev, plugins.sourcemaps.init()))
    // 使用 ts 转换插件
    .pipe(plugins.if(/\.ts$/, plugins.typescript({ module: 'commonjs', rootDir: cwd, noImplicitAny: true })))
    // 使用 js 语法转换兼容性插件
    .pipe(plugins.babel({ presets: [require('@babel/preset-env')] }))
    // 使用 js 压缩插件
    // .pipe(plugins.uglify())
    // 使用文件重命名插件
    .pipe(plugins.rename({ suffix: '.min' })) // 为js压缩文件添加后缀名 .min
    // 使用 sourcemaps 写入映射
    .pipe(plugins.if(isDev, plugins.sourcemaps.write('.')))
    /* 写入流 */
    .pipe(dest(buildPath))
    .pipe(bs.reload({ stream: true })) // 取代 bs init 下 files 监听目录配置
}

// 定义图片/图标相关任务 image
const image = () => {
  return src(imagesPath, { base: srcPath, cwd: srcPath })
    // 使用图片压缩插件
    .pipe(plugins.imagemin())
    // .pipe(plugins.webp()) // png图片转换为webp
    .pipe(dest(buildPath))
}
// 定义图标相关任务 icon
const icon = () => {
  return src(iconsPath, { base: srcPath, cwd: srcPath })
    // 使用图标压缩插件
    .pipe(plugins.imagemin())
    .pipe(dest(buildPath))
}
// 定义字体相关任务 font
const font = () => {
  return src(fontsPath, { base: srcPath, cwd: srcPath })
    // 使用字体压缩插件
    .pipe(plugins.imagemin())
    .pipe(dest(buildPath))
}

// 定义静态文件复制任务 copy
const copy = () => {
  return src(`${staticPath}/**`)
    /* .pipe(plugins.notify({
      message: "复制文件: <%= file.relative %> <<%= options.date %>>",
      templateOptions: {
        date: new Date().toLocaleString()
      }
    })) */
    .pipe(dest(buildPath))
}

// 定义服务监听任务 serve
const serve = () => {
  // 监听文件改变并执行相应任务
  watch(pagesPath, { cwd: srcPath }, page)
  watch(stylesPath, { cwd: srcPath }, style)
  watch(scriptsPath, { cwd: srcPath }, script)
  // 以下资源监听与打包一般是在生产环境才会用到
  // 监听资源文件修改变化后 bs.reload 重载
  watch([imagesPath, iconsPath, fontsPath], { cwd: srcPath }, bs.reload)
  watch(`${staticPath}/**`, bs.reload)
  // bs 服务初始化
  bs.init({
    notify, // bs 连接提醒(浏览器右上角)
    port, // 端口号配置
    open, // 关闭自动打开页面
    // files: `${buildPath}/**`, // 指定监听变化路径（页面自动更新同步）- 也可以在 gulp 任务流后 .pipe(bs.reload({ stream: true })) files 监听目录配置取代 files
    server: { // 服务配置
      // baseDir: buildPath, // 服务启动的目录 会自动找目录下的 index.html 文件
      baseDir: [buildPath, srcPath, staticPath], // 配置目录使资源构建减少提高效率
      routes: { // 路径映射配置（图片等资源显示异常处理）
        [`/${srcPath}`]: srcPath,
        '/node_modules': 'node_modules'
      }
    }
  })
}

// 定义解析 html 路径引用服务 ref
const ref = () => {
  return src(`${buildPath}/${pagesPath}`, { base: buildPath })
    .pipe(plugins.useref())
    // 压缩 html css js 代码
    .pipe(plugins.if(/\.html$/, plugins.htmlmin({
      collapseWhitespace: true,
      minifyCSS: true,
      minifyJS: true,
      removeComments: true // 移除注释
    })))
    .pipe(plugins.if(/\.css$/, plugins.cleanCss({ compatibility: 'ie8' })))
    .pipe(plugins.if(/\.js$/, plugins.uglify()))
    .pipe(dest(buildPath))
}

// 定义编译执行任务
const compile = parallel(page, style, script)

// 定义开发环境执行任务
const dev = series(compile, serve)

// 定义生产环境执行任务
const build = series(clean, compile, ref, parallel(image, icon, font, copy))

module.exports = {
  clean,
  dev,
  build
}
